<?php

declare(strict_types=1);

namespace app\service\settlement;

use app\model\OrgMember;
use app\model\Payout;
use app\model\PlatformBalance;
use app\model\PlatformBalanceLog;
use app\model\SellerStatement;
use app\service\notify\CreateNotifyService;
use app\util\GenNoUtil;
use InvalidArgumentException;
use think\facade\Db;
use think\Paginator;

class PayoutService
{
    public function __construct(
        private StatementService $statementService,
        private CreateNotifyService $notifyService
    ) {
    }

    public function paginate(array $filters, int $page, int $pageSize): Paginator
    {
        $query = Payout::with(['statement'])
            ->order('id', 'desc');

        if (!empty($filters['statement_id'])) {
            $query->where('statement_id', (int) $filters['statement_id']);
        }

        if (!empty($filters['seller_org_id'])) {
            $query->where('seller_org_id', (int) $filters['seller_org_id']);
        }

        if (array_key_exists('status', $filters) && $filters['status'] !== null && $filters['status'] !== '') {
            $query->where('status', (int) $filters['status']);
        }

        if (!empty($filters['keyword'])) {
            $keyword = trim((string) $filters['keyword']);
            $query->where(function ($subQuery) use ($keyword) {
                $subQuery->whereLike('payout_no', '%' . $keyword . '%')
                    ->whereOr('remark', 'like', '%' . $keyword . '%');
            });
        }

        return $query->paginate([
            'list_rows' => $pageSize,
            'page' => $page,
        ]);
    }

    public function get(int $id): Payout
    {
        $payout = Payout::with(['statement', 'org'])->find($id);
        if (!$payout) {
            throw new InvalidArgumentException('打款记录不存在');
        }

        return $payout;
    }

    public function create(array $payload): Payout
    {
        $statementId = (int) ($payload['statement_id'] ?? 0);
        if ($statementId <= 0) {
            throw new InvalidArgumentException('账单参数不正确');
        }

        /** @var SellerStatement|null $statement */
        $statement = SellerStatement::find($statementId);
        if (!$statement) {
            throw new InvalidArgumentException('关联账单不存在');
        }

        $sellerOrgId = (int) ($payload['seller_org_id'] ?? $statement->seller_org_id);
        if ($sellerOrgId !== (int) $statement->seller_org_id) {
            throw new InvalidArgumentException('账单与卖家组织不匹配');
        }

        $amount = round(max((float) ($payload['amount'] ?? 0), 0), 2);
        if ($amount <= 0) {
            throw new InvalidArgumentException('打款金额必须大于0');
        }

        $payout = Db::transaction(function () use ($statement, $sellerOrgId, $payload, $amount) {
            /** @var Payout $record */
            $record = Payout::create([
                'payout_no' => GenNoUtil::payout(),
                'statement_id' => (int) $statement->id,
                'seller_org_id' => $sellerOrgId,
                'amount' => $amount,
                'currency' => (string) ($payload['currency'] ?? 'CNY'),
                'pay_method' => (string) ($payload['pay_method'] ?? 'bank'),
                'pay_account' => (string) ($payload['pay_account'] ?? ''),
                'status' => Payout::STATUS_PENDING,
                'scheduled_at' => isset($payload['scheduled_at']) ? (string) $payload['scheduled_at'] : null,
                'remark' => (string) ($payload['remark'] ?? ''),
            ]);

            return Payout::with(['statement', 'org'])->find((int) $record->id);
        });

        return $payout;
    }

    public function update(Payout $payout, array $payload): Payout
    {
        $targetStatus = array_key_exists('status', $payload) ? (int) $payload['status'] : null;

        [$updated, $statementSnapshot, $paidAmount, $shouldNotify] = Db::transaction(function () use ($payout, $payload, $targetStatus) {
            /** @var Payout|null $locked */
            $locked = Payout::lock(true)->find((int) $payout->id);
            if (!$locked) {
                throw new InvalidArgumentException('打款记录不存在');
            }

            /** @var SellerStatement|null $statement */
            $statement = SellerStatement::lock(true)->find((int) $locked->statement_id);
            if (!$statement) {
                throw new InvalidArgumentException('关联账单不存在');
            }

            if (isset($payload['pay_method'])) {
                $locked->pay_method = (string) $payload['pay_method'];
            }

            if (isset($payload['pay_account'])) {
                $locked->pay_account = (string) $payload['pay_account'];
            }

            if (isset($payload['remark'])) {
                $locked->remark = (string) $payload['remark'];
            }

            if (isset($payload['scheduled_at'])) {
                $locked->scheduled_at = $payload['scheduled_at'] ? (string) $payload['scheduled_at'] : null;
            }

            if (array_key_exists('amount', $payload)) {
                $amount = round(max((float) $payload['amount'], 0), 2);
                if ($amount <= 0) {
                    throw new InvalidArgumentException('打款金额必须大于0');
                }
                if ((int) $locked->status === Payout::STATUS_PAID) {
                    throw new InvalidArgumentException('已支付的记录不允许修改金额');
                }
                $locked->amount = $amount;
            }

            $shouldNotify = false;
            $paidAmount = null;
            $now = date('Y-m-d H:i:s');

            if ($targetStatus !== null) {
                if (!in_array($targetStatus, [Payout::STATUS_PENDING, Payout::STATUS_PROCESSING, Payout::STATUS_PAID, Payout::STATUS_FAILED], true)) {
                    throw new InvalidArgumentException('打款状态不合法');
                }

                if ($targetStatus !== Payout::STATUS_PAID && (int) $locked->status === Payout::STATUS_PAID) {
                    throw new InvalidArgumentException('已支付的记录不允许变更状态');
                }

                if ($targetStatus === Payout::STATUS_PAID && (int) $locked->status !== Payout::STATUS_PAID) {
                    $locked->status = Payout::STATUS_PAID;
                    $locked->paid_at = $now;

                    $this->adjustPlatformBalance(
                        (float) $locked->amount,
                        'escrow',
                        'payout',
                        (int) $locked->id,
                        '结算打款'
                    );

                    $previousPaid = Payout::where('statement_id', $statement->id)
                        ->where('status', Payout::STATUS_PAID)
                        ->where('id', '<>', $locked->id)
                        ->sum('amount');

                    $paidSum = round((float) $previousPaid + (float) $locked->amount, 2);

                    $paidSum = round((float) $paidSum, 2);
                    if ($paidSum - (float) $statement->payable_amount > 0.01) {
                        throw new InvalidArgumentException('累计打款金额不能超过应付金额');
                    }

                    $statement->paid_amount = $paidSum;
                    $statement->save();

                    if ($paidSum + 0.01 >= (float) $statement->payable_amount) {
                        $paidAmount = min($paidSum, (float) $statement->payable_amount);
                        $shouldNotify = (int) $statement->status < SellerStatement::STATUS_PAID;
                    } else {
                        $paidAmount = null;
                        $shouldNotify = false;
                    }
                } else {
                    $locked->status = $targetStatus;
                    if ($targetStatus !== Payout::STATUS_PAID) {
                        $locked->paid_at = null;
                    }
                }
            }

            $locked->save();

            return [
                Payout::with(['statement', 'org'])->find((int) $locked->id),
                SellerStatement::find((int) $statement->id),
                $paidAmount !== null ? round((float) $paidAmount, 2) : null,
                $shouldNotify,
            ];
        });

        if ($paidAmount !== null && $statementSnapshot) {
            $statementAfter = $this->statementService->transition(
                $statementSnapshot,
                SellerStatement::STATUS_PAID,
                ['paid_amount' => $paidAmount],
                false
            );

            if ($shouldNotify) {
                $this->notifyPayout($statementAfter, (float) $updated->amount);
            }
        }

        return $this->get((int) $updated->id);
    }

    private function adjustPlatformBalance(
        float $amount,
        string $accountType,
        string $relatedType,
        int $relatedId,
        string $description
    ): void {
        $now = date('Y-m-d H:i:s');

        /** @var PlatformBalance|null $balance */
        $balance = PlatformBalance::lock(true)->where('account_type', $accountType)->find();

        if (!$balance) {
            $balance = PlatformBalance::create([
                'account_type' => $accountType,
                'balance' => 0,
                'currency' => 'CNY',
                'updated_at' => $now,
            ]);
        }

        $before = (float) $balance->balance;
        $after = round($before - $amount, 2);

        $balance->balance = $after;
        $balance->updated_at = $now;
        $balance->save();

        PlatformBalanceLog::create([
            'balance_id' => (int) $balance->id,
            'related_type' => $relatedType,
            'related_id' => $relatedId,
            'direction' => 'out',
            'amount' => $amount,
            'before_balance' => $before,
            'after_balance' => $after,
            'description' => $description,
        ]);
    }

    private function notifyPayout(SellerStatement $statement, float $amount): void
    {
        $targets = $this->collectOrgTargets((int) $statement->seller_org_id);
        if ($targets === []) {
            return;
        }

        $this->notifyService->add($targets, 'settlement_payout', [
            'statement_no' => $statement->statement_no,
            'amount' => round($amount, 2),
        ]);
    }

    private function collectOrgTargets(int $orgId): array
    {
        $userIds = OrgMember::where('org_id', $orgId)
            ->whereIn('role', [OrgMember::ROLE_OWNER, OrgMember::ROLE_ADMIN])
            ->column('user_id');

        $targets = [];
        foreach ($userIds as $id) {
            if ($id) {
                $targets[] = [
                    'id' => (int) $id,
                    'type' => 'seller',
                ];
            }
        }

        return $targets;
    }
}
